#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <setjmp.h>
#include <poll.h>

static jmp_buf jp_bf;

#define BUF_SIZE 32

struct dst_st
{
    int fd;
    char *off;
    ssize_t len;
};

enum
{
    STATE_READ = 0,
    STATE_WRITE,
    STATE_ERROR,
    STATE_TERM,
};

int main(int argc, char **argv)
{
    if (argc < 3)
    {
        fprintf(stderr, "Copy from source file to multi file, Usage: %s dst0 dst1\n", argv[0]);
        exit(1);
    }

    int nfds = 0;
    int src = open(argv[1], O_RDONLY);
    if (src < 0)
    {
        perror("open()");
        exit(1);
    }

    int dst_len = argc - 2;

    struct dst_st *dsts = malloc(dst_len * sizeof(struct dst_st));
    struct pollfd *polls = malloc(dst_len * sizeof(struct dst_st));

    if (dsts == NULL)
    {
        fprintf(stderr, "%s", "ENOMEM");
        exit(1);
    }

    for (int i = 0; i < dst_len; i++)
    {
        dsts[i].fd = open(argv[i + 2], O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, 0755);
        if (dsts[i].fd < 0)
        {
            perror("open()");
            exit(1);
        }

        if (dsts[i].fd > nfds)
            nfds = dsts[i].fd;
    }

    nfds++;

    int state = STATE_READ;
    char buf[BUF_SIZE];
    char *err_msg = NULL;
    ssize_t read_len = 0;

    while (state != STATE_TERM)
    {
        switch (state)
        {
        case STATE_READ:
        {
            read_len = read(src, buf, BUF_SIZE);
            if (read_len == 0)
            {
                state = STATE_TERM;
                break;
            }

            if (read_len < 0)
            {
                if (errno == EINTR)
                    break;
                state = STATE_ERROR;
                err_msg = strerror(errno);
                break;
            }

            state = STATE_WRITE;

            for (int i = 0; i < dst_len; i++)
            {
                dsts[i].off = buf;
                dsts[i].len = read_len;
            }

            break;
        }

        case STATE_WRITE:
        {
            for (int i = 0; i < dst_len; i++)
                polls[i].fd = dsts[i].fd;

            setjmp(jp_bf);
            int uncompleted = 0;

            for (int i = 0; i < dst_len; i++)
            {
                polls[i].events = POLLOUT;
                polls[i].revents = 0;
            }

            // block here
            while (1)
            {
                int polled = poll(polls, nfds, -1);
                if (polled < 0)
                {
                    if (errno == EINTR)
                    {
                        continue;
                    }
                    perror("poll()");
                    exit(1);
                }
                else
                {
                    break;
                }
            };

            for (int i = 0; i < dst_len; i++)
            {
                if (polls[i].fd < 0)
                    continue;
                if (polls[i].revents & POLLERR)
                {
                    state = STATE_ERROR;
                    err_msg = strerror(errno);
                    break;
                }

                if (!(polls[i].revents & POLLOUT))
                {
                    continue;
                }

                ssize_t writed = write(dsts[i].fd, dsts[i].off, dsts[i].len);
                while (writed < 0 && errno == EAGAIN)
                {
                    writed = write(dsts[i].fd, dsts[i].off, dsts[i].len);
                }

                if (writed < 0)
                {
                    err_msg = strerror(errno);
                    break;
                }

                dsts[i].off += writed;
                dsts[i].len -= writed;
            }

            for (int i = 0; i < dst_len; i++)
            {
                if (dsts[i].len == 0)
                    polls[i].fd = -1;
                uncompleted = uncompleted || dsts[i].len != 0;
            }

            if (uncompleted)
            {
                longjmp(jp_bf, 0);
            }
            else
            {
                state = STATE_READ;
            }
            break;
        }
        case STATE_ERROR:
        {
            fputs("error:\n", stderr);
            if (err_msg != NULL)
                fprintf(stderr, "%s\n", err_msg);
            state = STATE_TERM;
            break;
        }
        default:
            break;
        }
    }

    close(src);
    for (int i = 0; i < dst_len; i++)
        close(dsts[i].fd);
    free(dsts);
}